package org.hive.testserver.domain.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.hive.testserver.domain.util.Clock;

@SuppressWarnings("unchecked")
public class TestRun implements Entity {

	private String name;
	private Project project;
	private SortedMap<String,String> properties;
	private List<Test> tests;
	private long started;
	private long duration;
	
	private transient Set<TestStateListener> testUpdatedListeners;

	public TestRun(String name) {
		this(name, new TreeMap(), Clock.getInstance());
	}
	
	public TestRun(String name, Map properties, Clock clock) {
		super();
		this.name = name;
		this.started = clock.currentTimeMillis();
		this.properties = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);
		this.properties.putAll(properties);
		this.tests = new ArrayList();
	}
	
	void setProject(Project project) {
		this.project = project;
	}

	public String getName() {
		return name;
	}

	public String getId() {
		return project.getId()+":"+getName();
	}

	public Project getProject() {
		return project;
	}

	public SortedMap<String,String> getContext() {
		return Collections.unmodifiableSortedMap(properties);
	}

	public void addTestExecution(Test fixture) {
		tests.add(fixture);
		fixture.setTestRun(this);
	}
	
	public List<Test> getTests() {
		return tests;
	}

	public TestState result() {
		TestState result = TestState.PASSED;
		for (Iterator iterator = tests.iterator(); iterator.hasNext();) {
			Test test = (Test) iterator.next();
			TestState testState = test.currentState();
			if (test.isFinished()) {
				if (testState == TestState.FAILED || testState == TestState.ABORTED) {
					result = TestState.FAILED;
				}
			}
		}
		return result;
	}
	
	public TestState currentStatus() {
		TestState result = TestState.UNTESTED;
		for (Iterator iterator = tests.iterator(); iterator.hasNext();) {
			Test test = (Test) iterator.next();
			TestState testState = test.currentState();
 			if (testState == TestState.RUNNING || result == TestState.RUNNING) {
				result = TestState.RUNNING;
			} else if (testState == TestState.FAILED || result == TestState.FAILED) {
				result = TestState.FAILED;
			} else if (testState == TestState.PASSED || result == TestState.PASSED) {
				result = TestState.PASSED;
			} else if (testState == TestState.ABORTED || result == TestState.ABORTED) {
				result = TestState.ABORTED;
			}
		}
		return result;
	}
	
	public void abortRun() {
		for (Iterator iterator = tests.iterator(); iterator.hasNext();) {
			Test test = (Test) iterator.next();
			TestState testState = test.currentState();
			if (testState == TestState.RUNNING || 
					testState == TestState.UNTESTED) {				
				test.updateState(TestState.ABORTED);
			}
			test.beFinished();
		}
	}
	
	public boolean isFinished() {
		for (Test test : tests) {
			if (!test.isFinished()) {
				return false;
			}
		}
		return true;
	}
	
	public void testUpdatedState(Test test) {
		for (TestStateListener listener : getTestUpdatedListeners()) {
			listener.onTestStateUpdated(test);
		}
	}
	
	public static interface TestStateListener {
		public void onTestStateUpdated(Test test);
	}

	public void addTestStateListener(TestStateListener listener) {
		getTestUpdatedListeners().add(listener);
	}

	private synchronized Set<TestStateListener> getTestUpdatedListeners() {
		if (testUpdatedListeners == null) {
			testUpdatedListeners = new HashSet<TestStateListener>();
		}
		return testUpdatedListeners;
	}

	public long getStarted() {
		return started;
	}
	
	public Test lookupTestByName(String testName) {
		Test test = lookupTestByNameOrNull(testName);
		if (test != null) {
			return test;
		} else {
			throw new IllegalArgumentException("Unknown test named: "+testName);
		}
	}
	
	public Test lookupTestByNameOrNull(String testName) {
		for (Test test : tests) {
			if (test.getName().equals(testName)) {
				return test;
			}
		}
		return null;
	}
	
	public long getDuration() {
		return duration;
	}

	public void setDuration(long duration) {
		this.duration = duration;
	}

}
